package com.ms.service.module.account.db;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.Transformers;

import redis.clients.jedis.Jedis;

import com.google.gson.Gson;
import com.ms.game.bank.db.CashLogModel;
import com.ms.game.db.dto.DataTransfer;
import com.ms.game.db.dto.TransactionsDetailsUtil;
import com.ms.game.db.util.UpdateQueryBuilder;
import com.ms.game.itembox.db.AccountItemChangeLogModel;
import com.ms.player.Player;
import com.ms.service.db.jedis.JedisManager;
import com.ms.service.module.account.Account;
import com.ms.service.module.account.AccountModule;
import com.xpec.c4.db.basic.BasicGameDAO;
import com.xpec.c4.db.hibernate.manager.HibernateLogManager;
import com.xpec.c4.db.hibernate.manager.HibernateManager;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.game.utility.GUIDFactory;
import com.xpec.c4.game.utility.GUIDType;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.service.module.ConsistentGroupName;
import com.xpec.c4.service.module.ModuleName;

/**
 * 账户DAO
 * 
 * @author zhongzhengkai
 * 
 */
public class AccountDAO extends BasicGameDAO {

	private static final Log log = LogFactory.getLog(AccountDAO.class);

	private static final String ACCOUNT_DATA_REDIS_KEY = "ACCOUNT_DATA";
	private static final String ACCOUNT_TO_COOP_REDIS_KEY = "ACCOUNT_TO_COOP";
	private static final String COOP_INFO_REDIS_KEY = "COOP_INFO";
	private static final String COOP_SN_REDIS_KEY = "COOP_SN";
	private static final String DATA_LOCK_REDIS_KEY = "DATA_LOCK";

	/** Redis : ACCOUNT_DATA */

	/**
	 * <pre>
	 * 讀取Account
	 * </pre>
	 * 
	 * @param accountId
	 *            帳戶編號
	 * @return 帳戶信息
	 */
	public static Account loadAccountFromRedis(GUID accountId) {
		if (accountId == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() accountId is null");
			}

			return null;
		}

		String cooperatorAccountId = getCooperatorAccountIdFromRedis(accountId);

		if (cooperatorAccountId == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() cooperatorAccountId is null");
			}

			return null;
		}

		return loadAccountFromRedis(cooperatorAccountId);
	}

	/**
	 * <pre>
	 * 讀取Account
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @return 帳戶信息
	 */
	public static Account loadAccountFromRedis(String cooperatorAccountId) {
		Map<String, String> coopInfos = getCoopInfo(cooperatorAccountId);

		if (coopInfos == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() coop info not exist, coopId = "
						+ cooperatorAccountId);
			}

			return null;
		}

		GUID accountId = GUID.parseUUID(coopInfos.get(CooperatorInfoField.ACCOUNT_ID));
		Account account = new Account();

		account.setAccountId(accountId);
		account.addPlayer(accountId);
		account.setCooperatorAccountId(Account.makeCoopId(coopInfos.get(CooperatorInfoField.EZ_NAME),
				coopInfos.get(CooperatorInfoField.OPEN_ID)));
		account.setRegisteredPlatform(coopInfos.get(CooperatorInfoField.PLATFORM));
		account.setVenderId(coopInfos.get(CooperatorInfoField.PARENT));
		account.setSubVenderId(coopInfos.get(CooperatorInfoField.EZ_NAME));

		return account;
	}

	/**
	 * <pre>
	 * 儲存Account
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @param account
	 *            帳戶信息
	 * @return true表示成功, false則否
	 */
	public static boolean saveAccountToRedis(String cooperatorAccountId, Account account) {
		if (!GameUtility.isHaveString(cooperatorAccountId)) {
			return false;
		}

		if (account == null) {
			return false;
		}

		// save account
		{
			String redisName = getAccountDataRedisName(account.getAccountId());
			Jedis jedis = JedisManager.getPlayerDataResource(redisName);

			if (jedis == null) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + "() get redis " + redisName
							+ " fail, jedis = null.");
				}

				return false;
			}

			String key = ACCOUNT_DATA_REDIS_KEY + ":" + account.getAccountId();
			String json = new Gson().toJson(DataTransfer.transfer(account));
			String result = jedis.set(key, json);

			JedisManager.returnPlayerDataResource(redisName, jedis);

			if (isSaveAccountToRedisSuccess(result) == false) {
				return false;
			}
		}

		// save accountId to cooperatorAccountId
		{
			String redisName = getAccountToCoopRedisName(account.getAccountId());
			Jedis jedis = JedisManager.getPlayerDataResource(redisName);

			if (jedis == null) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + "() get redis " + redisName
							+ " fail, jedis = null.");
				}

				return false;
			}

			String key = ACCOUNT_TO_COOP_REDIS_KEY + ":" + account.getAccountId().toString();
			String json = cooperatorAccountId;
			String result = jedis.set(key, json);

			JedisManager.returnPlayerDataResource(redisName, jedis);

			if (isSaveAccountToRedisSuccess(result) == false) {
				return false;
			}
		}

		return true;
	}

	private static boolean isSaveAccountToRedisSuccess(String result) {
		return result != null && result.equals("OK");
	}

	/**
	 * <pre>
	 * 刪除Account
	 * </pre>
	 * 
	 * @param accountId
	 *            帳戶編號
	 * @return true表示成功, false則否
	 */
	public static boolean deleteAccountFromRedis(GUID accountId) {
		if (accountId == null) {
			return false;
		}

		// delete account
		{
			String redisName = getAccountDataRedisName(accountId);
			Jedis jedis = JedisManager.getPlayerDataResource(redisName);

			if (jedis == null) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + "() get redis " + redisName
							+ " fail, jedis = null.");
				}

				return false;
			}

			String key = ACCOUNT_DATA_REDIS_KEY + ":" + accountId.toString();

			jedis.del(key);

			JedisManager.returnPlayerDataResource(redisName, jedis);
		}

		// delete accountId to cooperatorAccountId
		{
			String redisName = getAccountToCoopRedisName(accountId);
			Jedis jedis = JedisManager.getPlayerDataResource(redisName);

			if (jedis == null) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + "() get redis " + redisName
							+ " fail, jedis = null.");
				}

				return false;
			}

			String key = ACCOUNT_TO_COOP_REDIS_KEY + ":" + accountId.toString();

			jedis.del(key);

			JedisManager.returnPlayerDataResource(redisName, jedis);
		}

		return true;
	}

	/** Redis : ACCOUNT_TO_COOP */

	/**
	 * <pre>
	 * 以帳戶編號取得營運商AccountId
	 * </pre>
	 * 
	 * @param accountId
	 *            帳戶編號
	 * @return 營運商AccountId
	 */
	public static String getCooperatorAccountIdFromRedis(GUID accountId) {
		if (accountId == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() accountId is null");
			}

			return null;
		}

		String redisName = getAccountToCoopRedisName(accountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail, jedis = null.");
			}

			return null;
		}

		String key = ACCOUNT_TO_COOP_REDIS_KEY + ":" + accountId.toString();
		String value = jedis.get(key);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		if (!GameUtility.isHaveString(value)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() coop id not exist, accountId = "
						+ accountId.toString());
			}

			return null;
		}

		return value;
	}

	/**
	 * <pre>
	 * 儲存帳戶編號與營運商AccountId的映射資料
	 * </pre>
	 * 
	 * @param accountId
	 *            帳戶編號
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @return true表示成功, false則否
	 */
	public static boolean saveCooperatorAccountIdToRedis(GUID accountId, String cooperatorAccountId) {
		if (accountId == null) {
			return false;
		}

		if (!GameUtility.isHaveString(cooperatorAccountId)) {
			return false;
		}

		String redisName = getAccountToCoopRedisName(accountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail, jedis = null.");
			}

			return false;
		}

		String key = ACCOUNT_TO_COOP_REDIS_KEY + ":" + accountId.toString();
		String json = cooperatorAccountId;
		String result = jedis.set(key, json);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		if (isSaveCooperatorAccountIdToRedisSuccess(result) == false) {
			return false;
		}

		return true;
	}

	private static boolean isSaveCooperatorAccountIdToRedisSuccess(String result) {
		return result != null && result.equals("OK");
	}

	/** Redis : COOP_INFO */

	/**
	 * <pre>
	 * 讀取營運帳號資訊
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @return 記錄營運帳號資訊的 map, key: {@link CooperatorInfoField}
	 */
	public static Map<String, String> getCoopInfo(String cooperatorAccountId) {
		if (!GameUtility.isHaveString(cooperatorAccountId)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() coopId is empty");
			}

			return null;
		}

		String redisName = AccountDAO.getCoopInfoRedisName(cooperatorAccountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return null;
		}

		String key = COOP_INFO_REDIS_KEY + ":" + cooperatorAccountId;
		Map<String, String> coopInfos = jedis.hgetAll(key);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		if (isGetCoopInfoSuccess(coopInfos) == false) {
			return null;
		}

		return coopInfos;
	}

	private static boolean isGetCoopInfoSuccess(Map<String, String> coopInfos) {
		return coopInfos != null && coopInfos.isEmpty() == false;
	}

	/**
	 * <pre>
	 * 讀取營運帳號資訊
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @param fieldName
	 *            欄位名稱 {@link CooperatorInfoField}
	 * @return 營運帳號資訊值, null表示失敗
	 */
	public static String getCoopInfoField(String cooperatorAccountId, String fieldName) {
		if (!GameUtility.isHaveString(cooperatorAccountId)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() coopId is empty");
			}

			return null;
		}

		String redisName = AccountDAO.getCoopInfoRedisName(cooperatorAccountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return null;
		}

		String key = COOP_INFO_REDIS_KEY + ":" + cooperatorAccountId;
		String fieldValue = jedis.hget(key, fieldName);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		return fieldValue;
	}

	/**
	 * <pre>
	 * 建立營運帳號資訊
	 * </pre>
	 * 
	 * @param ezName
	 *            營運商代號
	 * @param platform
	 * @param parent
	 * @param domain
	 * @param openId
	 *            現金網會員 open id
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @return 記錄營運帳號資訊的 map, key: {@link CooperatorInfoField}
	 */
	public static Map<String, String> createCoopInfo(String ezName, String platform, String parent, String domain,
			String openId, String cooperatorAccountId) {
		if (!GameUtility.isHaveString(ezName)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() ezName is empty");
			}

			return null;
		}

		if (!GameUtility.isHaveString(openId)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() openId is empty");
			}

			return null;
		}

		if (!GameUtility.isHaveString(cooperatorAccountId)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() coopId is empty");
			}

			return null;
		}

		long coopSn = nextCoopSn(ezName);

		if (coopSn == 0) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get new coop sn fail");
			}

			return null;
		}

		String redisName = AccountDAO.getCoopInfoRedisName(cooperatorAccountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return null;
		}

		GUID accountId = GUIDFactory.createUUID(GUIDType.TYPE_ACCOUNT);
		String playerName = Player.makePlayerName(ezName, String.valueOf(coopSn));
		HashMap<String, String> coopInfos = new HashMap<String, String>();

		coopInfos.put(CooperatorInfoField.EZ_NAME, ezName);
		coopInfos.put(CooperatorInfoField.PLATFORM, platform);
		coopInfos.put(CooperatorInfoField.PARENT, parent);
		coopInfos.put(CooperatorInfoField.DOMAIN, domain);
		coopInfos.put(CooperatorInfoField.OPEN_ID, openId);
		coopInfos.put(CooperatorInfoField.ACCOUNT_ID, accountId.toString());
		coopInfos.put(CooperatorInfoField.PLAYER_ID, accountId.toString());
		coopInfos.put(CooperatorInfoField.PLAYER_NAME, playerName);
		coopInfos.put(CooperatorInfoField.COOP_SN, String.valueOf(coopSn));

		String key = COOP_INFO_REDIS_KEY + ":" + cooperatorAccountId;
		String result = jedis.hmset(key, coopInfos);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		if (isCreateCoopInfoSuccess(result) == false) {
			return null;
		}

		return coopInfos;
	}

	private static boolean isCreateCoopInfoSuccess(String result) {
		return result != null && result.equals("OK");
	}

	/**
	 * <pre>
	 * 儲存營運帳號資訊
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @param fieldName
	 *            欄位名稱 {@link CooperatorInfoField}
	 * @param fieldvalue
	 *            欄位值
	 * @return true表示成功, false則否
	 */
	public static boolean saveCoopInfoField(String cooperatorAccountId, String fieldName, String fieldvalue) {
		if (!GameUtility.isHaveString(cooperatorAccountId)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() coopId is empty");
			}

			return false;
		}

		String redisName = AccountDAO.getCoopInfoRedisName(cooperatorAccountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return false;
		}

		String key = COOP_INFO_REDIS_KEY + ":" + cooperatorAccountId;
		long result = jedis.hset(key, fieldName, fieldvalue);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		if (isSaveCoopInfoFieldSuccess(result) == false) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() save fail, res=" + result + ", coopId="
						+ cooperatorAccountId + " fieldName=" + fieldName + " fieldvalue=" + fieldvalue);
			}

			return false;
		}

		return true;
	}

	private static boolean isSaveCoopInfoFieldSuccess(long result) {
		return result == 1 || result == 0;
	}

	/**
	 * <pre>
	 * 刪除營運帳號資訊
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @param fieldName
	 *            欄位名稱 {@link CooperatorInfoField}
	 * @return true表示成功, false則否
	 */
	public static boolean deleteCoopInfoField(String cooperatorAccountId, String fieldName) {
		if (!GameUtility.isHaveString(cooperatorAccountId)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() coopId is empty");
			}

			return false;
		}

		String redisName = AccountDAO.getCoopInfoRedisName(cooperatorAccountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return false;
		}

		String key = COOP_INFO_REDIS_KEY + ":" + cooperatorAccountId;
		long result = jedis.hdel(key, fieldName);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		if (isDeleteCoopInfoFieldSuccess(result) == false) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() delete fail, field " + fieldName
						+ " not exist. coopId=" + cooperatorAccountId);
			}

			return false;
		}

		return true;
	}

	private static boolean isDeleteCoopInfoFieldSuccess(long result) {
		return result != 0;
	}

	/** Redis : COOP_SN */

	/**
	 * <pre>
	 * 取得現金網會員流水號
	 * </pre>
	 * 
	 * @param ezName
	 *            營運商代號
	 * @return 會員流水號, 0表示失敗, 大於0表示成功
	 */
	private static long nextCoopSn(String ezName) {
		if (!GameUtility.isHaveString(ezName)) {
			return 0;
		}

		String redisName = ModuleName.LOGIN;
		Jedis jedis = JedisManager.getResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return 0;
		}

		Long coopSn = jedis.hincrBy(COOP_SN_REDIS_KEY, ezName, 1);

		JedisManager.returnResource(redisName, jedis);

		return coopSn;
	}

	/** Redis : DATA_LOCK **/

	/**
	 * <pre>
	 * 用營運商AccountId建立簡易的 redis資料鎖
	 * 防止資料被多方同時存取產生亂七八糟的問題
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 * @return true表示成功, false則否
	 */
	public static boolean dataLock(String cooperatorAccountId) {
		String redisName = AccountDAO.getCoopInfoRedisName(cooperatorAccountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return false;
		}

		/**
		 * <pre>
		 * NX: only set the key if not exist
		 * EX: expire time unit is second
		 * </pre>
		 */

		String key = DATA_LOCK_REDIS_KEY + ":" + cooperatorAccountId;
		String value = "1";
		String result = jedis.set(key, value, "NX", "EX", 30);

		JedisManager.returnPlayerDataResource(redisName, jedis);

		// 無法建立, 表示已經被建過了 lock 失敗
		if (result == null) {
			return false;
		}

		return true;
	}

	/**
	 * <pre>
	 * 用營運商AccountId解開redis資料鎖
	 * </pre>
	 * 
	 * @param cooperatorAccountId
	 *            營運商AccountId, call {@link Account #makeCoopId(String, String)}
	 *            to make cooperatorAccountId
	 */
	public static void dataUnlock(String cooperatorAccountId) {
		String redisName = AccountDAO.getCoopInfoRedisName(cooperatorAccountId);
		Jedis jedis = JedisManager.getPlayerDataResource(redisName);

		if (jedis == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + "() get redis " + redisName + " fail");
			}

			return;
		}

		String key = DATA_LOCK_REDIS_KEY + ":" + cooperatorAccountId;

		jedis.del(key);

		JedisManager.returnPlayerDataResource(redisName, jedis);
	}

	/** Redis : 工具函式 */

	private static String getAccountDataRedisName(GUID accountId) {
		return ConsistentGroupName.lookUp(ModuleName.GROUP_PLAYER_DATA_REDIS, accountId);
	}

	private static String getAccountToCoopRedisName(GUID accountId) {
		return ConsistentGroupName.lookUp(ModuleName.GROUP_PLAYER_DATA_REDIS, accountId);
	}

	private static String getCoopInfoRedisName(String coopId) {
		return ConsistentGroupName.lookUpByString(ModuleName.GROUP_PLAYER_DATA_REDIS, coopId);
	}

	/** 其他函式 */

	/**
	 * 保存账户数据
	 * 
	 * @param player
	 */
	public static boolean saveAccount(Account account) {
		log.info("DB saveAccount " + account);
		AccountModel model = DataTransfer.transfer(account);
		if (model.getTime() == null) {
			model.setTime(Calendar.getInstance());
		}
		HibernateManager.txBegin();

		GameUtility.writeCashUpdateConsoleLog("AccountDAO.saveAccount()", model.getAccountId(), 0, 0,
				model.getCash());
		save(model);
		return HibernateManager.txCommitFailRollbackClearSession();
	}

	/**
	 * 保存或更新账户数据
	 * 
	 * @param account
	 */
	public static boolean saveOrUpdateAccount(Account account) {
		log.info("DB saveOrUpdateAccount " + account);
		AccountModel accountModel = loadAccountModelByAccountId(account.getAccountId());
		accountModel = DataTransfer.transfer(account, accountModel);
		if (accountModel.getDb_id() == null && accountModel.getTime() == null) {
			accountModel.setTime(Calendar.getInstance());
		}
		Session session = HibernateManager.txBegin();
		GameUtility.writeCashUpdateConsoleLog("AccountDAO.saveOrUpdateAccount()", accountModel.getAccountId(), 0,
				0, accountModel.getCash());
		session.saveOrUpdate(accountModel);
		return HibernateManager.txCommitFailRollbackClearSession();
	}

	/**
	 * 保存账户数据
	 * 
	 * @param player
	 */
	public static boolean saveAccounts(List<Account> accounts) {
		log.info("DB saveAccounts " + accounts);
		List<AccountModel> models = DataTransfer.transferAccounts(accounts);
		HibernateManager.txBegin();
		for (AccountModel accountModel : models) {
			if (accountModel.getTime() == null) {
				accountModel.setTime(Calendar.getInstance());
			}

			GameUtility.writeCashUpdateConsoleLog("AccountDAO.saveAccounts()", accountModel.getAccountId(), 0, 0,
					accountModel.getCash());
			save(accountModel);
		}
		return HibernateManager.txCommitFailRollbackClearSession();
	}

	/**
	 * 保存或更新多个账户数据
	 * 
	 * @param account
	 * @return
	 */
	public static boolean saveOrUpdateAccounts(List<Account> accounts) {
		log.info("DB saveOrUpdateAccounts " + accounts);
		List<String> accountIds = new ArrayList<String>();
		Map<String, Account> mapVO = new HashMap<String, Account>();
		for (Account account : accounts) {
			if (account != null) {
				String stringID = GUID.toString(account.getAccountId());
				accountIds.add(stringID);
				mapVO.put(stringID, account);
			}
		}
		List<AccountModel> accountModels = loadAccountModelsByAccountId(accountIds);

		HibernateManager.txBegin();
		for (AccountModel accountModel : accountModels) {
			if (accountModel.getDb_id() == null && accountModel.getTime() == null) {
				accountModel.setTime(Calendar.getInstance());
			}

			GameUtility.writeCashUpdateConsoleLog("AccountDAO.saveOrUpdateAccounts()",
					accountModel.getAccountId(), 0, 0, accountModel.getCash());
			saveOrUpdate(DataTransfer.transfer(mapVO.get(accountModel.getAccountId()), accountModel));
		}
		return HibernateManager.txCommitFailRollbackClearSession();
	}

	/**
	 * 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static Account loadAccountByAccountId(GUID accountId) {
		if (accountId == null) {
			if (log.isErrorEnabled()) {
				log.error("DB loadAccountByAccountId, accountId is null");
			}
			return null;
		}
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("accountId", GUID.toString(accountId)));
		AccountModel model = (AccountModel) criteria.uniqueResult();
		HibernateManager.endSession();

		if (log.isInfoEnabled()) {
			log.info("account loadAccountByAccountId DB : " + accountId);
		}

		return DataTransfer.transfer(model);
	}

	public static Account loadAccountByCooperatorAccountId(String accountId) {
		if (accountId == null) {
			if (log.isErrorEnabled()) {
				log.error("DB loadAccountByCooperatorAccountId, accountId is null");
			}
			return null;
		}
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("cooperatorAccountId", accountId));
		AccountModel model = (AccountModel) criteria.uniqueResult();
		HibernateManager.endSession();

		if (log.isInfoEnabled()) {
			log.info("account loadAccountByCooperatorAccountId DB : " + accountId);
		}

		return DataTransfer.transfer(model);
	}

	/**
	 * 忽略账户id大小写 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static Account loadAccountByAccountIdIgnoreCase(GUID accountId) {
		if (accountId == null) {
			if (log.isErrorEnabled()) {
				log.error("DB loadAccountByAccountIdIgnoreCase, accountId is null");
			}
			return null;
		}
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("accountId", GUID.toString(accountId)).ignoreCase());
		AccountModel model = (AccountModel) criteria.uniqueResult();
		HibernateManager.endSession();

		if (log.isInfoEnabled()) {
			log.info("account loadAccountByAccountId DB : " + accountId);
		}

		return DataTransfer.transfer(model);
	}

	/**
	 * 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static List<Account> loadAccountsByAccountId(List<String> accountIds) {
		log.info("DB loadAccountsByAccountId " + accountIds);
		if (accountIds == null || accountIds.size() == 0) {
			return null;
		}
		List<AccountModel> models = loadAccountModelsByAccountId(accountIds);
		return DataTransfer.transferAccountModels(models);
	}

	/**
	 * 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static AccountModel loadAccountModelByAccountId(GUID accountId) {
		log.info("DB loadAccountModelByAccountId " + accountId);
		if (accountId == null) {
			return null;
		}
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("accountId", GUID.toString(accountId)));
		AccountModel model = (AccountModel) criteria.uniqueResult();
		HibernateManager.endSession();
		return model;
	}

	/**
	 * 新增一個API取得AccountModel
	 * 忽略账户id大小写 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static AccountModel loadAccountModelByAccountId(String accountId) {
		log.info("DB loadAccountModelByAccountId " + accountId);
		if (accountId == null) {
			return null;
		}
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("accountId", accountId));
		AccountModel model = (AccountModel) criteria.uniqueResult();
		HibernateManager.endSession();
		return model;
	}

	/**
	 * 忽略账户id大小写 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static AccountModel loadAccountModelByAccountIdIgnoreCase(GUID accountId) {
		log.info("DB loadAccountModelByAccountId " + accountId);
		if (accountId == null) {
			return null;
		}
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("accountId", GUID.toString(accountId)).ignoreCase());
		AccountModel model = (AccountModel) criteria.uniqueResult();
		HibernateManager.endSession();
		return model;
	}

	/**
	 * 忽略账户id大小写 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static AccountModel loadAccountModelByAccountIdIgnoreCase(String accountId) {
		log.info("DB loadAccountModelByAccountId " + accountId);
		if (accountId == null) {
			return null;
		}
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("accountId", accountId).ignoreCase());
		AccountModel model = (AccountModel) criteria.uniqueResult();
		HibernateManager.endSession();
		return model;
	}

	/**
	 * 通过账户ID列表获取账户数据列表
	 * 
	 * @param accountId
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static List<AccountModel> loadAccountModelsByAccountId(List<String> accountIds) {
		log.info("DB loadAccountModelsByAccountId " + accountIds);
		if (accountIds == null || accountIds.size() == 0) {
			return null;
		}
		Session session = HibernateManager.getSession();
		List<AccountModel> models = session.createQuery("From AccountModel where accountId in(:accountIds)")
				.setParameterList("accountIds", accountIds).list();
		HibernateManager.endSession();
		return models;
	}

	/**
	 * 通过账户ID删除账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static boolean deleteAccountByAccountId(GUID accountId) {
		log.info("DB deleteAccountByAccountId " + accountId);
		Session session = HibernateManager.getSession();
		HibernateManager.txBegin();
		Query query = session.createQuery("Delete From AccountModel model where model.accountId = :accountId")
				.setParameter("accountId", GUID.toString(accountId));
		int result = query.executeUpdate();
		HibernateManager.txCommitFailRollbackClearSession();
		if (result > 0) {
			return true;
		}
		return false;
	}

	/**
	 * 通过账户ID获取账户名称
	 * 
	 * @param accountId
	 * @return
	 */
	public static String getAccountNameByAccountId(GUID accountId) {
		log.info("DB getAccountNameByAccountId " + accountId);
		Session session = HibernateManager.getSession();
		Query query = session.createQuery("select name from Account where accountId = :accountId");
		query.setParameter("accountId", GUID.toString(accountId));
		String name = (String) query.uniqueResult();
		HibernateManager.endSession();
		return name;
	}

	/**
	 * get accountId from cooperatorAccountId
	 * 
	 * @param cooperatorAccountId
	 * @return
	 */
	public static String getAccountIdByCooperatorAccountId(String cooperatorAccountId) {
		log.info("DB getAccountNameByAccountId " + cooperatorAccountId);
		Session session = HibernateManager.getSession();
		Query query = session
				.createQuery("select am.accountId from AccountModel am where am.cooperatorAccountId = :_cooperatorAccountId");
		query.setParameter("_cooperatorAccountId", cooperatorAccountId);
		String name = (String) query.uniqueResult();
		HibernateManager.endSession();
		return name;
	}

	/**
	 * get accountId from cooperatorAccountId
	 * 
	 * @param cooperatorAccountId
	 * @return
	 */
	public static void queryPlayerListByPortalAccountIdList(List<String> portalAccountIdList, int portalId,
			List<GUID> ignorelAccountIdList, List<GUID> ignorelPlayerGuidIdList) {

		log.info("DB getPlayaerListByPortalAccountIdList");
		Session session = HibernateManager.getSession();
		Query query = session
				.createQuery("select am.accountId as accountId, am.players as players from AccountModel am where am.portalAccountId in (:portalAccountIdList) and am.portalId = :portalId");
		query.setParameterList("portalAccountIdList", portalAccountIdList);
		query.setParameter("portalId", portalId);
		query.setResultTransformer(Transformers.aliasToBean(AccountModel.class));
		List<AccountModel> accountModelList = query.list();
		for (AccountModel accountModel : accountModelList) {
			GUID accountGuid = GUID.parseUUID(accountModel.getAccountId());
			if (accountGuid != null) {
				ignorelAccountIdList.add(accountGuid);
			}
			if (accountModel.getPlayers() != null) {
				for (String playerGuidStr : accountModel.getPlayers()) {
					GUID playerGuid = GUID.parseUUID(playerGuidStr);
					if (playerGuid != null) {
						ignorelPlayerGuidIdList.add(playerGuid);
					}
				}
			}
		}

		HibernateManager.endSession();
		return;
	}

	/**
	 * 通过账户ID获取账户数据
	 * 
	 * @param accountId
	 * @return
	 */
	public static List<Account> loadAccounts(int startCount, int maxCount) {
		log.info("DB loadAccounts " + startCount + ":" + maxCount);
		if (maxCount == 0) {
			return null;
		}

		Session session = HibernateManager.getSession();

		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.addOrder(Order.desc("db_id"));
		criteria.setFirstResult(startCount);
		criteria.setMaxResults(maxCount);
		@SuppressWarnings("unchecked")
		List<AccountModel> models = criteria.list();

		HibernateManager.endSession();

		return DataTransfer.transferAccountModels(models);
	}

	/**
	 * 用立即玩編號產生帳號的名稱
	 * 
	 * @param trialNumber
	 * @return
	 */
	public static String getTrialAccountName(int trialAccountGroup, int trialNumber) {
		// 2017-08-24 將原本7個位元拆分成以 "群組" + "角色編號" 構成
		String groupStr = GameUtility.formatNumber(trialAccountGroup, "00");// group位元
		String numStr = GameUtility.formatNumber(trialNumber, "00000");// trialNumber位元

		String accountName = AccountModule.TRIAL_ACCOUNT_NAME + "_" + groupStr + numStr;

		return accountName;
	}

	/**
	 * 建立立即玩帳號
	 * 
	 * @param trialNum
	 *            立即玩編號
	 * @return 成功: 立即玩帳號<br>
	 *         失敗: null
	 */
	public static Account createTrialAccountByTrialNumber(int trialAccountGroup, int trialNum) {
		Account account = new Account();
		account.setAccountId(GUIDFactory.createUUID(GUIDType.TYPE_ACCOUNT));

		String coopAccountId = getTrialAccountName(trialAccountGroup, trialNum);
		account.setCooperatorAccountId(coopAccountId);

		account.setLoadTime(System.currentTimeMillis());
		account.setPlayers(new ArrayList<GUID>());
		account.setTrialNumber(trialNum);// 因此不同群組的體驗帳號會出現相同的trialNumber

		GameUtility.writeCashUpdateConsoleLog("AccountDAO.createTrialAccountByTrialNumber()", account
				.getAccountId().toString(), 0, 0, account.getCash());
		if (!saveOrUpdateAccount(account)) {
			return null;
		}

		return account;
	}

	/**
	 * 取得所有立即玩帳號數量
	 * 
	 * @return
	 */
	public static int getTrialAccountCount() {
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);

		// 符合立即玩編號
		Criterion criTrialNum = Restrictions.ne("trialNumber", Account.NORMAL_PLAYER_TRIAL_NUMBER);
		criteria.add(criTrialNum);

		// 取得數量
		Number count = (Number) criteria.setProjection(Projections.rowCount()).uniqueResult();

		HibernateManager.endSession();

		return count.intValue();
	}

	/**
	 * 載入所有未綁定營運商 id 的帳號 (立即玩、試玩帳號）
	 * 
	 * @return Account Id list
	 */
	public static List<GUID> loadUnusedTrialAccount() {
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);

		// 立即玩編號 != 0
		Criterion criTrialNum = Restrictions.ne("trialNumber", Account.NORMAL_PLAYER_TRIAL_NUMBER);
		criteria.add(criTrialNum);

		// 立即玩編號 != -1
		Criterion criDeviceTrialNum = Restrictions.ne("trialNumber", Account.DEVICE_TRIAL_PLAYER_NUMBER);
		criteria.add(criDeviceTrialNum);

		// 只取 AccountId 欄位
		criteria.setProjection(Projections.projectionList().add(Projections.property("accountId")));

		// 取得資料
		@SuppressWarnings("unchecked")
		List<String> resultList = criteria.list();

		// 準備回傳的 list
		List<GUID> rtnGuidList = new ArrayList<GUID>();

		// 結果放入回傳的 list 中
		for (String guid : resultList) {
			rtnGuidList.add(GUID.parseUUID(guid));
		}

		return rtnGuidList;
	}

	/**
	 * 載入所有所有使用中的立即玩編號
	 * 
	 * @return trial number list
	 */
	public static List<Integer> loadAllTrialNumberList() {
		Session session = HibernateManager.getSession();
		Criteria criteria = session.createCriteria(AccountModel.class);

		// 立即玩編號 != 0
		Criterion criTrialNum = Restrictions.ne("trialNumber", 0);

		criteria.add(criTrialNum);

		// 只取 AccountId 欄位
		criteria.setProjection(Projections.projectionList().add(Projections.property("trialNumber")));

		// 取得資料
		@SuppressWarnings("unchecked")
		List<Integer> rtnNumList = criteria.list();

		return rtnNumList;
	}

	/**
	 * 透過帳號ID讀取帳號點數資訊
	 * 
	 * @param accountId
	 * @return
	 */
	public static Long loadCashByAccountId(GUID accountID) {
		log.info("DB loadCashByAccountId " + accountID);
		if (accountID == null) {
			return null;
		}
		Session session = HibernateManager.getSession();
		Query query = session.createQuery("select cash from AccountModel where accountId = :accountId)");
		query.setParameter("accountId", accountID.toString());
		Long cash = (Long) query.uniqueResult();
		HibernateManager.endSession();
		return cash;
	}

	/**
	 * 載入所有的 tcp proxy 資料
	 * 
	 * @return
	 */
	public static ArrayList<TcpProxyServerGroupListModel> loadTcpProxyList() {
		Session session = HibernateManager.getSession();

		Criteria criteria = session.createCriteria(TcpProxyServerGroupListModel.class);
		@SuppressWarnings("unchecked")
		List<TcpProxyServerGroupListModel> list = criteria.list();
		HibernateManager.endSession();

		ArrayList<TcpProxyServerGroupListModel> resultList = new ArrayList<TcpProxyServerGroupListModel>();
		resultList.addAll(list);

		return resultList;
	}

	/**
	 * 儲值流程中判斷是否有同樣的ID {@link CashLogModel purchasId}
	 * {@link AccountItemChangeLogModel purchaseId}
	 * 
	 * @param purchas_id
	 * @return
	 */
	public static boolean checkPurchasIdInRecharge(String purchas_id) {
		Session session = HibernateLogManager.getSession();
		Query query = session.createQuery("select count(c) from CashLogModel c where purchasId = :ID");
		query.setParameter("ID", purchas_id);
		Long count = (Long) query.uniqueResult();
		if (count > 0) {
			HibernateManager.endSession();
			return true;
		}

		session.clear();
		query = session.createQuery("select count(c) from AccountItemChangeLogModel c where purchaseId = :ID");
		query.setParameter("ID", purchas_id);
		count = (Long) query.uniqueResult();
		HibernateLogManager.endSession();

		if (count > 0) {
			return true;
		}
		return false;
	}

	public static Object[] getAccountMaxLvAndViplv(String accountId) {
		Session session = HibernateManager.getSession();
		Query query = session
				.createQuery("select max(vip),max(level) from PlayerModel  where accountId= :accountId");
		query.setParameter("accountId", accountId);
		Object[] list = (Object[]) query.uniqueResult();
		return list;
	}

	public static ArrayList<CashLogModel> getCashTransactionDetails(String playerId, String accountId, long from,
			long end) {
		from = TransactionsDetailsUtil.checkTransactionDateRange(from);
		end = TransactionsDetailsUtil.checkTransactionDateRange(end);
		if (from == end) {
			return null;
		}
		Session session = HibernateLogManager.getSession();
		SQLQuery query = session.createSQLQuery("select * from cash_point_log where accountId = :accountId "
				+ " and  (playerId is null or playerId = :playerId)"
				+ " and (gameTime > :end and gameTime < :from) order by gameTime desc");
		query.addEntity(CashLogModel.class);
		query.setParameter("playerId", playerId);
		query.setParameter("accountId", accountId);
		query.setParameter("end", end);
		query.setParameter("from", from);
		ArrayList<CashLogModel> cashLogModels = (ArrayList<CashLogModel>) query.list();
		HibernateLogManager.endSession();
		return cashLogModels;
	}

	public static AccountModel loadRobotAccountByAccountId(String accountId) {
		Session session = HibernateManager.txBegin();
		Query query = session
				.createQuery("From AccountModel where accountId = :ACCOUNTID and cooperatorAccountId in (Select accid from RobotDataModel)");
		query.setParameter("ACCOUNTID", accountId);
		AccountModel model = (AccountModel) query.uniqueResult();
		HibernateManager.txCommitFailRollbackClearSession();
		if (model != null)
			return model;
		else
			return null;
	}

	public static boolean updateAccountBanLoginTime(String accountId, long loginBanTime, String banLoginTimeReson) {
		Session session = HibernateManager.txBegin();
		UpdateQueryBuilder builder = new UpdateQueryBuilder();
		builder.setSession(session);
		builder.setTableName("AccountModel");
		builder.addSetValue("loginBanTime", loginBanTime);
		builder.addSetValue("banLoginTimeReson", banLoginTimeReson);
		builder.addWhereKey("accountId", accountId);
		builder.executeQuery();
		return HibernateManager.txCommitFailRollbackClearSession();
	}

	/**
	 * 從最後鎖定角色取得帳號資訊
	 * 
	 * @param lastLockPlayerGUID
	 * @return
	 */
	public static AccountModel getAccountModelByLastLockPlayerGUID(String lastLockPlayerGUID) {
		if (lastLockPlayerGUID == null) {
			return null;
		}

		Session session = HibernateManager.txBegin();
		Query query = session.createQuery("From AccountModel where lastLockPlayerGUID = :GUID");
		query.setParameter("GUID", lastLockPlayerGUID);
		AccountModel model = (AccountModel) query.uniqueResult();
		HibernateManager.txCommitFailRollbackClearSession();

		return model;
	}

	/**
	 * 更新最後鎖定角色、deviceToken
	 * 
	 * @param accountID
	 * @param playerGUID
	 * @param deviceToken
	 */
	public static boolean updateAccountLockPlayerAndDeviceToken(String accountID, String playerGUID,
			String deviceToken) {
		if (accountID == null)
			return false;

		Session session = HibernateManager.txBegin();
		UpdateQueryBuilder builder = new UpdateQueryBuilder();
		builder.setSession(session);
		builder.setTableName("AccountModel");
		builder.addSetValue("deviceToken", deviceToken);
		builder.addSetValue("lastLockPlayerGUID", playerGUID);

		builder.setWhereKey("accountId", accountID);
		builder.executeQuery();
		return HibernateManager.txCommitFailRollbackClearSession();
	}

	/**
	 * 創見角色後更新 account model, 只更新必要的欄位
	 * 
	 * @param account
	 */
	public static void updateAccountForCreateCharacter(Account account) {
		if (account == null) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "account=null");
			}
			return;
		}

		AccountModel accountModel = DataTransfer.transfer(account, null);

		Session session = HibernateManager.txBegin();
		UpdateQueryBuilder builder = new UpdateQueryBuilder(session);
		builder.setTableName("AccountModel");

		builder.addSetValue("players", accountModel.getPlayers());
		builder.addSetValue("createdPlayerCount", accountModel.getCreatedPlayerCount());
		builder.addSetValue("playerSerialNumMap", accountModel.getPlayerSerialNumMap());
		builder.addSetValue("uplineCode", accountModel.getUplineCode());
		builder.addSetValue("inviteCodeErrorCount", accountModel.getInviteCodeErrorCount());
		builder.addSetValue("inviteCodeUnlockTime", accountModel.getInviteCodeUnlockTime());

		builder.setWhereKey("accountId", accountModel.getAccountId());
		builder.executeQuery();
		boolean res = HibernateManager.txCommitFailRollbackClearSession();

		if (!res) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "update account data fail, a:" + account.getAccountId());
			}
		}
	}

	/**
	 * 由deviceID讀取帳號，若帳號不存在則創建一個帳號
	 */
	public static Account loadOrCreateAccountByDeviceID(String platformStr, String deviceID) {
		if (deviceID == null) {
			return null;
		}

		Session session = HibernateManager.txBegin();

		// 將deviceID視為全局唯一
		Criteria criteria = session.createCriteria(AccountModel.class);
		criteria.add(Restrictions.eq("deviceID", deviceID));

		AccountModel model = null;

		Object result = criteria.uniqueResult();

		if (result != null) {
			model = (AccountModel) result;
		} else {
			String accountID = GUIDFactory.createUUID(GUIDType.TYPE_ACCOUNT).toString();

			model = new AccountModel();
			model.setAccountId(accountID);
			model.setCooperatorAccountId(AccountModule.TRIAL_DEVICE_ACCOUNT_NAME + deviceID);
			model.setTrialNumber(Account.DEVICE_TRIAL_PLAYER_NUMBER);
			model.setDeviceID(deviceID);
			model.setPlayers(new ArrayList<String>());
			model.setTime(Calendar.getInstance());
			model.setRegisteredPlatform(platformStr);

			session.save(model);
		}

		boolean res = HibernateManager.txCommitFailRollbackClearSession();
		if (res) {
			return DataTransfer.transfer(model);
		} else {
			return null;
		}
	}
}
